package ltd.dujiabao.dex.core.registrar;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import ltd.dujiabao.dex.core.DelayJob;
import ltd.dujiabao.dex.core.DelayJobEntity;
import ltd.dujiabao.dex.core.ExecutorType;
import ltd.dujiabao.dex.core.emitter.EmitterFactoryBean;

/**
 * @author : DuJiabao
 * @Project : common-delay-executor
 * @Time : 2024/3/25 20:13
 * @Desc :
 */
public class DelayJobBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware, ResourceLoaderAware {
    private Environment environment;
    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider delayJobScanner = getDelayJobScanner();
        delayJobScanner.setResourceLoader(resourceLoader);
        delayJobScanner.addIncludeFilter(new AnnotationTypeFilter(DelayJob.class));

        Set<BeanDefinition> beanDefinitionSet = new HashSet<>();
        Set<DelayJobEntity> entities = new HashSet<>();
        Set<String> basePackages = getBasePackages(importingClassMetadata);
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidateComponents = delayJobScanner.findCandidateComponents(basePackage);
            for (BeanDefinition candidateComponent : candidateComponents) {
                if (!(candidateComponent instanceof AnnotatedBeanDefinition)) {
                    continue;
                }
                if (beanDefinitionSet.contains(candidateComponent)) {
                    continue;
                }
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                Assert.isTrue(annotationMetadata.isInterface(),
                        "@DelayJob can only be specified on an interface");
                beanDefinitionSet.add(beanDefinition);

                Map<String, Object> attributes = annotationMetadata
                        .getAnnotationAttributes(DelayJob.class.getCanonicalName());
                DelayJobEntity entity = toEntity(annotationMetadata, attributes);
                entities.add(entity);
                registerJobEmitter(registry, entity);
            }
        }
        registerDelayJobEntityHolder(registry, entities);
    }

    private DelayJobEntity toEntity(AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        return new DelayJobEntity()
                .setJobName((String) attributes.get("jobName"))
                .setDelayTime((long) attributes.get("delayTime"))
                .setTimeUnit((TimeUnit) attributes.get("timeUnit"))
                .setExecutorType((ExecutorType) attributes.get("executorType"))
                .setImplementation((Class<?>) attributes.get("implementation"))
                .setClassName(annotationMetadata.getClassName());
    }

    private void registerJobEmitter(BeanDefinitionRegistry registry, DelayJobEntity delayJobEntity) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(EmitterFactoryBean.class);
        builder.addPropertyValue("type", delayJobEntity.getClassName());
        builder.addPropertyValue("delayJob", delayJobEntity);
        builder.addPropertyReference("emitterFactoryHolder", "emitterFactoryHolder");
        builder.setPrimary(true);
        registry.registerBeanDefinition(delayJobEntity.identity() + "FactoryBean", builder.getBeanDefinition());
    }

    private void registerDelayJobEntityHolder(BeanDefinitionRegistry registry, Set<DelayJobEntity> entities) {
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(DelayJobEntityHolder.class)
                .addPropertyValue("delayJobEntities", entities)
                .getBeanDefinition();
        registry.registerBeanDefinition("delayJobEntityHolder", beanDefinition);
    }

    protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> attributes = importingClassMetadata
                .getAnnotationAttributes(EnableDelayJob.class.getCanonicalName());

        Set<String> basePackages = new HashSet<>();
        for (String pkg : (String[]) attributes.get("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (Class<?> clazz : (Class<?>[]) attributes.get("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }

        if (basePackages.isEmpty()) {
            basePackages.add(
                    ClassUtils.getPackageName(importingClassMetadata.getClassName())
            );
        }
        return basePackages;
    }

    private ClassPathScanningCandidateComponentProvider getDelayJobScanner() {
        return new ClassPathScanningCandidateComponentProvider(false, environment) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                boolean isCandidate = false;
                if (beanDefinition.getMetadata().isIndependent()) {
                    if (!beanDefinition.getMetadata().isAnnotation()) {
                        isCandidate = true;
                    }
                }
                return isCandidate;
            }
        };
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}
